﻿// <copyright file="SchemeUtils.cs" company="Joe Chung">
// Copyright © 2008 by Joe Chung.
// </copyright>
namespace NScheme
{
    using System;
    using System.Text;

    public abstract class SchemeUtils
    {
        public static readonly bool TRUE = true;

        public static readonly bool FALSE = false;

        public static readonly double ZERO = 0.0D;

        public static readonly double ONE = 1.0D;

        public static char chr(object x)
        {
            try
            {
                return (char)x;
            }
            catch (InvalidCastException)
            {
                return chr(error("expected a char, got: " + x));
            }
        }

        public static Pair cons(object a, object b)
        {
            return new Pair(a, b);
        }

        public static bool equal(object x, object y)
        {
            if (x == null || y == null)
            {
                return x == y;
            }
            else if (x is char[])
            {
                if (!(y is char[]))
                {
                    return false;
                }

                char[] xc = (char[])x;
                char[] yc = (char[])y;
                if (xc.Length != yc.Length)
                {
                    return false;
                }

                for (int i = xc.Length - 1; i >= 0; i--)
                {
                    if (xc[i] != yc[i])
                    {
                        return false;
                    }
                }

                return true;
            }
            else if (x is object[])
            {
                if (!(y is object[]))
                {
                    return false;
                }

                object[] xo = (object[])x;
                object[] yo = (object[])y;
                if (xo.Length != yo.Length)
                {
                    return false;
                }

                for (int i = xo.Length - 1; i >= 0; i--)
                {
                    if (!xo[i].Equals(yo[i]))
                    {
                        return false;
                    }
                }

                return true;
            }
            else
            {
                return x.Equals(y);
            }
        }

        public static object error(string message)
        {
            Console.Error.WriteLine("**** ERROR: " + message);
            throw new Exception(message);
        }

        public static bool eqv(object x, object y)
        {
            return x == y || (x is double && x.Equals(y)) || (x is char && x.Equals(y));
        }

        public static object first(object x)
        {
            return x is Pair ? ((Pair)x).first : null;
        }

        public static InputPort inPort(object x, Scheme interp)
        {
            try
            {
                return x == null ? interp.input : (InputPort)x;
            }
            catch (InvalidCastException)
            {
                return inPort(error("expected an input port, got: " + x), interp);
            }
        }

        public static int length(object x)
        {
            int len = 0;
            while (x is Pair)
            {
                len++;
                x = ((Pair)x).rest;
            }

            return len;
        }

        public static Pair list(object a)
        {
            return new Pair(a, null);
        }

        public static Pair list(object a, object b)
        {
            return new Pair(a, new Pair(b, null));
        }

        public static object listStar(object args)
        {
            return rest(args) == null ? first(args) : cons(first(args), listStar(rest(args)));
        }

        public static char[] listToString(object chars)
        {
            char[] str = new char[length(chars)];

            int i = 0;
            while (chars is Pair)
            {
                str[i] = chr(first(chars));
                chars = rest(chars);
                i++;
            }

            return str;
        }

        public static object[] listToVector(object objs)
        {
            object[] vec = new object[length(objs)];

            for (int i = 0; objs is Pair; i++)
            {
                vec[i] = first(objs);
                objs = rest(objs);
            }

            return vec;
        }

        public static double num(object x)
        {
            try
            {
                return Convert.ToDouble(x);
            }
            catch (InvalidCastException)
            {
                return num(error("expected a number, got: " + x));
            }
        }

        public static PrintWriter outPort(object x, Scheme interp)
        {
            try
            {
                return x == null ? interp.output : (PrintWriter)x;
            }
            catch (InvalidCastException)
            {
                return outPort(error("expected an output port, got: " + x), interp);
            }
        }

        protected static object p(object x)
        {
            Console.Out.WriteLine(stringify(x));
            return x;
        }

        protected static object p(string msg, object x)
        {
            Console.Out.WriteLine(msg + ": " + stringify(x));
            return x;
        }

        public static object rest(object x)
        {
            return x is Pair ? ((Pair)x).rest : null;
        }

        public static object reverse(object x)
        {
            object result = null;
            while (x is Pair)
            {
                result = cons(first(x), result);
                x = rest(x);
            }

            return result;
        }

        public static object second(object x)
        {
            return first(rest(x));
        }

        public static object setFirst(object x, object y)
        {
            return x is Pair ? ((Pair)x).first = y : error("Attempt to set-car of a non-Pair: " + stringify(x));
        }

        public static object setRest(object x, object y)
        {
            return x is Pair ? ((Pair)x).rest = y : error("Attempt to set-cdr of a non-Pair: " + stringify(x));
        }

        public static char[] str(object x)
        {
            try
            {
                return (char[])x;
            }
            catch (InvalidCastException)
            {
                return str(error("expected a string, got: " + x));
            }
        }

        public static string stringify(object x)
        {
            return stringify(x, true);
        }

        public static string stringify(object x, bool quoted)
        {
            StringBuilder buf = new StringBuilder();
            stringify(x, quoted, buf);
            return buf.ToString();
        }

        public static void stringify(object x, bool quoted, StringBuilder buf)
        {
            if (x == null)
            {
                buf.Append("()");
            }
            else if (x is double)
            {
                double d = (double)x;
                if (Math.Round(d) == d)
                {
                    buf.Append((long)d);
                }
                else
                {
                    buf.Append(d);
                }
            }
            else if (x is char)
            {
                if (quoted)
                {
                    buf.Append("#\\");
                }

                buf.Append(x);
            }
            else if (x is Pair)
            {
                ((Pair)x).stringifyPair(quoted, buf);
            }
            else if (x is char[])
            {
                char[] chars = (char[])x;
                if (quoted)
                {
                    buf.Append('"');
                }

                for (int i = 0; i < chars.Length; i++)
                {
                    if (quoted && chars[i] == '"')
                    {
                        buf.Append('\\');
                    }

                    buf.Append(chars[i]);
                }

                if (quoted)
                {
                    buf.Append('"');
                }
            }
            else if (x is object[])
            {
                object[] v = (object[])x;
                buf.Append("#(");
                for (int i = 0; i < v.Length; i++)
                {
                    stringify(v[i], quoted, buf);
                    if (i != v.Length - 1)
                    {
                        buf.Append(' ');
                    }
                }

                buf.Append(')');
            }
            else if (ObjectHelper.IsTrue(x))
            {
                buf.Append("#t");
            }
            else if (ObjectHelper.IsFalse(x))
            {
                buf.Append("#f");
            }
            else
            {
                buf.Append(x);
            }
        }

        public static string sym(object x)
        {
            try
            {
                return (string)x;
            }
            catch (InvalidCastException)
            {
                return sym(error("expected a symbol, got: " + x));
            }
        }

        public static object third(object x)
        {
            return first(rest(rest(x)));
        }

        public static bool truth(object x)
        {
            return !ObjectHelper.IsFalse(x);
        }

        public static object[] vec(object x)
        {
            try
            {
                return (object[])x;
            }
            catch (InvalidCastException)
            {
                return vec(error("expected a vector, got: " + x));
            }
        }

        public static Pair vectorToList(object x)
        {
            if (x is object[])
            {
                object[] vec = (object[])x;
                Pair result = null;
                for (int i = vec.Length - 1; i >= 0; i--)
                {
                    result = cons(vec[i], result);
                }

                return result;
            }
            else
            {
                error("expected a vector, got: " + x);
                return null;
            }
        }

        public static object warn(string message)
        {
            Console.Error.WriteLine("**** WARNING: " + message);
            return "<warn>";
        }

        public static object write(object x, PrintWriter port, bool quoted)
        {
            port.print(stringify(x, quoted));
            port.flush();
            return x;
        }
    }
}